import pandas as pd
import streamlit as st
from code import code_one

# 设置页面布局为宽屏模式
st.set_page_config(layout="wide")

choice = st.sidebar.selectbox("请选择", ["前向传播", "反向传播", "代码示例及演示"])

if choice == "前向传播":
    st.title("前向传播")
    with st.form("form1"):
        st.markdown("## 一、前向传播介绍 ##")
        st.markdown("""
        ### 1. 前向传播的定义 ###
        - 前向传播是指在一个机器学习算法中，从输入到输出的信息传递过程，具体来说，就是在数据输入后，经过一系列的运算后得到结果的过程。也就是：输入x 经过一系列计算f(x) 得到y的过程。
        比如y=2x+3,这个公式，前向传播就是通过给定x，根据公式2x+3得到输出结果y的值的过程就是前向传播。
        ### 2. 前向传播的过程 ###
        - step1：输入层，输入数据首先需要进入输入层，每一个神经元都会接收一个信号(输入值(矩阵))
        - step2:  输入层到隐藏层，输入层的输出作为下一层的(通常是隐藏层)，通过与权重相乘加上偏置项后进行非线性变换‌，使用激活函数对隐藏层的输出进行非线性变换，以引入非线性特性，增强模型的表达能力。
        - step3:  隐藏层到输出层‌：将隐藏层的输出乘以隐藏层到输出层的权重矩阵，再加上偏置，计算输出层的输出。
        ### 2.3 前向传播的作用 ###
        - 对数据的输入逐步处理，提取对应的特征，并进行预测。
          - w	权重
          - b	偏置
        """)
        col1, col2 = st.columns([10, 1])
        with col2:
            submit = st.form_submit_button("我已学习")
        if submit:
            st.success("恭喜你，你已看完本小节内容")
            st.balloons()
    with st.form("form2"):
        st.markdown("## 二、损失函数介绍 ##")
        st.markdown('<div id="target"></div>', unsafe_allow_html=True)
        st.markdown("""
        ### 1. 损失函数的概念 ###
        损失函数（Loss Function）是用来衡量模型预测结果与实际结果之间的差异的一种函数。在机器学习中，损失函数通常被用来优化模型，通过最小化损失函数来提高模型的预测准确率。
        其中一种损失函数——均方差（MSE）。原理是：		torch.nn.MSEloss()
        - a.计算所有样本点对应的真实值 与 拟合线的预测值 的距离，让其尽可能小即最小。   sum（y预测 - y真实）** 2
        - b.因为有的点在线上方，有的点在线的下方，直接求距离会有正有负，使用平方来抵消正负问题。
        - c.又因为样本很多的时候，这个距离的平方和很大，所以我们对其求平均值。
        """)
        col1, col2 = st.columns([10, 1])
        with col2:
            submit = st.form_submit_button("我已学习")
        if submit:
            st.success("恭喜你，你已通过本章，请换下一章继续学习吧")
            st.balloons()
elif choice == "反向传播":
    st.title("反向传播")
    with st.form("form1"):
        st.markdown("## 一、反向传播介绍 ##")
        st.markdown("""
        ### 1. 反向传播的学习率与梯度下降起什么作用？ ###
        反向传播的学习率与梯度下降起的作用就是对前向传播中的参数进行更新(改变参数的值)。
        ### 2. 反向传播的概念 ###
        - 反向传播是在前向传播后进行的，它是对前向传播过程中的参数进行更新的一个过程，通过最小化损失函数来优化模型参数。
        - 在前向传播过程中，我们已经“拟合”出了一条直线，但令损失函数 
        - 值最小的直线是我们一次次的通过手动修改参数w的值来得到的，而我们希望在随便输入一个参数后，这个参数可以根据“我们的期望”去“自动修改”，而不是耗费人力来不断修改参数。基于此，有了反向传播这个过程。
        ### 3. 前向传播与反向传播的区别 ###
        前向传播是在参数固定后，向公式中传入参数，进行预测的一个过程。当参数值选择的不恰当时，会导致最后的预测值不符合我们的预期，于是我们就需要重新修改参数值。
        反向传播是在前向传播后进行的，它是对参数进行更新的一个过程，反向传播的过程中参数会根据某些规律修改从而改变损失函数的值。
        """)
        col1, col2 = st.columns([10, 1])
        with col2:
            submit = st.form_submit_button("我已学习")
        if submit:
            st.success("恭喜你，你已看完本小节内容")
            st.balloons()
    with st.form("form2"):
        st.markdown("## 二、梯度下降介绍 ##")
        st.markdown("""
        ### 1. 梯度下降的概念 ###
        - 在机器学习中，梯度表示损失函数对于模型参数的偏导数，梯度下降是机器学习中一种常用的优化算法。
        - 它的基本思想是在训练过程中通过不断调整参数，使损失函数（代表模型预测结果与真实结果之间的差距）达到最小值。
        为了实现这一目标，梯度下降算法会计算损失函数的梯度（带方向的斜率），然后根据梯度的方向更新权重，使损失函数不断减小。
        对于一个模型来说，我们可以计算每一个权重对损失函数的影响程度，然后根据损失函数的梯度来更新这些权重。通过不断重复这一过程，我们就可以找到一组使损失函数最小的权重值，从而训练出一个优秀的模型。
        - 在机器学习中，梯度表示损失函数对于模型参数的偏导数。具体来说，对于每个可训练参数，梯度告诉我们在当前参数值下，沿着每个参数方向变化时，损失函数的变化率。
        通过计算损失函数对参数的梯度，梯度下降算法能够根据梯度的信息来调整参数，朝着减少损失的方向更新模型，从而逐步优化模型，使得模型性能更好。
        #### 1.1 优化方法讲解 ####
        在损失函数关于w的表达式中，确实存在一个w，使得损失函数的值最小。下面使用三种方案，包括固定值法、斜率法、小固定值*斜率法，展示一下其不同的优化效果。
        ##### 01 方案1：固定值法 #####
        - w新=w旧−固定值
        - 固定值的弊端有两个：
            - 1.当固定值的正负确定后，就意味着w的更新方向确定了，比如w>0时，且固定值为正数时，w的值是不断减小的，w<0时，固定值为负数时，w的值是不断变大的。
            - 2.不能够准确的更新到令损失值最小的w值，比如最合适的w值为0.8，现在的w值为1，而固定值为0.5，那么就会跳过0.8，直接变成0.5，并且如果还没迭代完，还会变成0，-0.5，…。
            也就是说，想通过该方法更新到最好的w值，要么事先算好，要么碰运气，总之效果很差。
        ##### 02 方案2：斜率法 #####
        - 在这里它就是损失函数曲线中的w点的切线，细心的同学已经发现了，斜率还有个很好的特性，那就是当w小于最低点对应的w时，它是负的；当w大于最低点对应的w时，它是正的。
        即当w当前>w最佳时：w新=w旧−正数；当w当>w最佳时：w新=w旧−负数。
        其公式如下所示：
        - w新=w旧−斜率
        - 斜率法的弊端：
        当损失函数比较陡峭时，斜率值很大，会来回震荡，并且斜率值越来越大，损失函数值也会越来越大，效果也不是很理想。
        ##### 03 方案3：小固定值*斜率法 #####
        - 斜率有很好的特性，但是斜率值太大了不受控制，那么是不是可以把斜率，也就是梯度的值乘以一下很小的数（小于1）。
        - 其公式如下所示：
            - w新=w旧−小固定值⋅斜率
            - 式子中的小固定值被称为学习率，通过改变学习率的值能调整w的更新速度，而整个式子就叫做梯度下降（Gradient Descent，GD），是一种优化函数，作用是最小化损失函数。
        """)
        col1, col2 = st.columns([10, 1])
        with col2:
            submit = st.form_submit_button("我已学习")
            if submit:
                st.success("恭喜你，你已看完本小节内容")
                st.balloons()
    with st.form("form3"):
        st.markdown("## 三、学习率介绍 ##")
        st.markdown("""
        ### 3.4 学习率的概念 ###
        学习率（Learning Rate）是机器学习和深度学习中最核心的超参数之一，它决定了模型在训练过程中参数更新的步长，直接影响模型的收敛速度、训练稳定性以及最终性能。
        学习率控制每次参数更新时，沿着梯度方向调整的幅度。
        - 公式：θ新 = θ旧 - α * ∇J(θ) 
        其中，θ是模型参数，∇J(θ) 是损失函数对参数的梯度。
        - 直观理解：
            - 1.步长过大（高学习率）：可能跳过最优解，导致震荡甚至发散。
            - 2.步长过小（低学习率）：收敛速度慢，容易陷入局部极小值。
        """)
        # 创建数据表格
        data = {
            "学习率设置": ["过高", "过低", "适中"],
            "训练行为特征": [
                "参数更新步长过大，损失值（Loss）呈现剧烈震荡，无法稳定下降",
                "每次参数更新幅度微小，损失值下降速度极慢，训练过程中曲线几乎平缓",
                "损失值稳定下降，下降速度与波动幅度平衡（无剧烈震荡，也无停滞）"
            ],
            "最终训练结果": [
                "模型无法收敛，甚至出现 **Loss 爆炸**（损失值急剧增大），最终无法得到有效训练的模型",
                "收敛时间过长（需消耗大量计算资源和时间），且可能因更新不足卡在 **局部最优点**，无法逼近全局最优解",
                "高效收敛，能在合理时间内找到全局最优解或优质局部最优解，平衡训练效率与模型精度"
            ]
        }

        # 显示表格
        st.subheader("学习率影响对比表")
        df = pd.DataFrame(data)
        st.dataframe(df, use_container_width=True)
        col1, col2 = st.columns([10, 1])
        with col2:
            submit = st.form_submit_button("我已学习")
        if submit:
            st.success("恭喜你，你已通过本章，请换下一章继续学习吧")
            st.balloons()
elif choice == "代码示例及演示":
    st.markdown("# 前向与反向传播——代码示例及演示 #")
    sample_code_one, sample_code_two = code_one.sample_code()
    with st.form("form1"):
        st.markdown("## 一、代码——数学方法实现线性回归 ##")
        with st.expander("点击展开查看代码"):
            st.code(sample_code_one, language="python")
        col1, col2 = st.columns([6, 1])
        with col2:
            submit = st.form_submit_button("我已知晓，运行代码")
        if submit:
            st.success("已运行该代码，结果显示如下：")
            code_one.one_sample_code()
    with st.form("form2"):
        st.markdown("## 二、代码——自求导方式实现线性回归 ##")
        with st.expander("点击展开查看代码"):
            st.code(sample_code_two, language="python")
        col1, col2 = st.columns([6, 1])
        with col2:
            submit = st.form_submit_button("我已知晓，运行代码")
        if submit:
            st.success("已运行该代码，结果显示如下：")
            code_one.sample_code_two()
